﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Common.Files
{
    /// <summary>
    /// 自定义日志类
    /// </summary>
    public class LogHelper
    {
        private static object lockObj = new object();
        #region 配置
        private static string GetConfig(string keyName)
        {
            return System.Configuration.ConfigurationManager.AppSettings[keyName];
        }
        private static string GetConfig(string keyName, string defaultValue = null)
        {
            string strValue = GetConfig(keyName);
            if (string.IsNullOrEmpty(strValue))
            {
                return defaultValue;
            }
            return strValue;
        }
        private static bool GetConfigToBool(string keyName, bool defaultValue = true)
        {
            string strConfig = GetConfig(keyName);
            return string.IsNullOrEmpty(strConfig) || strConfig.ToLower() != (!defaultValue).ToString().ToLower() ? defaultValue : !defaultValue;
        }
        #region 开启日志记录类型
        /// <summary>
        /// 是否开启致命日志
        /// isLogFatal
        /// 默认开启
        /// </summary>
        private static bool IsFatalEnabled { get { return GetConfigToBool("isLogFatal", true); } }
        /// <summary>
        /// 是否开启错误日志
        /// isLogError
        /// 默认开启
        /// </summary>
        private static bool IsErrorEnabled { get { return GetConfigToBool("isLogError", true); } }
        /// <summary>
        /// 是否开启警告日志
        /// isLogWarn
        /// 默认不开启
        /// </summary>
        private static bool IsWarnEnabled { get { return GetConfigToBool("isLogWarn", false); } }
        /// <summary>
        /// 是否开启信息日志
        /// isLogInfo
        /// 默认不开启
        /// </summary>
        private static bool IsInfoEnabled { get { return GetConfigToBool("isLogInfo", false); } }
        /// <summary>
        /// 是否开启测试日志
        /// isLogDebug
        /// 默认不开启
        /// </summary>
        private static bool IsDebugEnabled { get { return GetConfigToBool("isLogDebug", false); } }
        #endregion

        #region 日志记录信息
        /// <summary>
        /// 是否显示日志类型
        /// ShowType
        /// 默认是
        /// 显示时日志类型会加在日志内容第一行
        /// </summary>
        private static readonly bool IsShowType = GetConfigToBool("LogShowType", true);
        /// <summary>
        /// 日志文件根目录
        /// Directory
        /// 默认为：“Log/Error”
        /// </summary>
        private static readonly string strDirectory = GetConfig("LogDirectory", "Log/Error");
        /// <summary>
        /// 是否创建二级目录
        /// IsCreateDirectory
        /// 默认开启
        /// 二级目录目录名称格式为yyyyMM
        /// </summary>
        private static readonly bool IsCreateDirectory = GetConfigToBool("LogIsCreateDirectory", true);
        /// <summary>
        /// 文件名称格式
        /// FileNameFormat
        /// 默认yyyy-MM-dd
        /// </summary>
        private static readonly string strLogFileNameFormat = GetConfig("LogFileNameFormat", "yyyy-MM-dd");
        /// <summary>
        /// 是否将日志内容加到文件末尾
        /// AppendToFile
        /// 默认追加到末尾
        /// </summary>
        private static readonly bool IsAppendToFile = GetConfigToBool("LogAppendToFile", true);
        /// <summary>
        /// 日志内容格式
        /// LogContentFormat
        /// 提供日志内容包括：执行时间(date)、提示消息(tip)、错误提示(prompt)、错误信息(comment)、操作方法(method)、项目名称(name)、操作位置(location)
        /// 默认"{dk}{dv}{tk}{tv}{pk}{pv}{ck}{cv}{mk}{mv}{nk}{nv}{lk}{lv}$n"
        /// 自定义示例1："$n执行时间{dv}错源方法{mv}{pk}{pv}{ck}{cv}{nk}{nv}{lk}{lv}$n"
        /// 自定义示例2："2{dk}=$n执行时间,{ck}=异常信息"
        /// 注意：
        /// 1.{*k}的自带如果没包含冒号"："或者":"会自带冒号"："
        /// 2.{*v}的自带末尾换行
        /// 3.示例1格式没写到的日志内容将不显示，示例2替换设置部分内容，其余内容以默认形式显示
        /// 4.需要换行的以$n标识
        /// </summary>
        private static readonly string strLogContentFormat = GetConfig("LogContentFormat", "{dk}{dv}{tk}{tv}{pk}{pv}{ck}{cv}{mk}{mv}{nk}{nv}{lk}{lv}$n");
        private static string strFatalContentFormat = string.Empty;
        private static string strErrorContentFormat = string.Empty;
        private static string strWarnContentFormat = string.Empty;
        private static string strInfoContentFormat = string.Empty;
        private static string strDebugContentFormat = string.Empty;
        #endregion
        #endregion

        #region 公用方法
        /// <summary>
        /// 根据日志类型获取对应日志格式
        /// </summary>
        /// <param name="logType"></param>
        /// <returns></returns>
        private static string GetContentFormat(LogType logType, string strNewLine)
        {
            string strContentFormat = string.Empty;
            switch (logType)
            {
                case LogType.Fatal:
                    strContentFormat = strFatalContentFormat;
                    break;
                case LogType.Error:
                    strContentFormat = strErrorContentFormat;
                    break;
                case LogType.Warn:
                    strContentFormat = strWarnContentFormat;
                    break;
                case LogType.Info:
                    strContentFormat = strInfoContentFormat;
                    break;
                case LogType.Debug:
                    strContentFormat = strDebugContentFormat;
                    break;
                default:
                    throw new KeyNotFoundException("找不到日志类型");
            }

            if (string.IsNullOrEmpty(strContentFormat))
            {
                if (strLogContentFormat.Substring(0, 1) == "2")
                {
                    List<KeyValuePair<string, string>> list = strLogContentFormat.TrimStart('2').Split(',').Select(s => new KeyValuePair<string, string>(s.Substring(0, s.IndexOf('=')), s.Substring(s.IndexOf('=') + 1))).ToList();
                    strContentFormat = FormatReplace(logType, list);
                }
                else
                {
                    strContentFormat = FormatReplace(logType, strLogContentFormat);
                }
                strContentFormat = strContentFormat.Replace("$n", strNewLine);
                SetContentFormat(logType, strContentFormat);
            }
            return strContentFormat;
        }
        /// <summary>
        /// 设置对应日志类型的日志格式
        /// </summary>
        /// <param name="logType"></param>
        /// <param name="ContentFormat"></param>
        private static void SetContentFormat(LogType logType, string ContentFormat)
        {
            switch (logType)
            {
                case LogType.Fatal:
                    strFatalContentFormat = ContentFormat;
                    break;
                case LogType.Error:
                    strErrorContentFormat = ContentFormat;
                    break;
                case LogType.Warn:
                    strWarnContentFormat = ContentFormat;
                    break;
                case LogType.Info:
                    strInfoContentFormat = ContentFormat;
                    break;
                case LogType.Debug:
                    strDebugContentFormat = ContentFormat;
                    break;
                default:
                    throw new KeyNotFoundException("找不到日志类型");
            }
        }
        /// <summary>
        /// 去掉相关字段
        /// </summary>
        /// <param name="str"></param>
        /// <param name="firld"></param>
        /// <returns></returns>
        private static string TrimFirld(string str, string firld)
        {
            if (str.Contains("{" + firld + "v}"))
            {
                if (str.Contains("{" + firld + "k}"))
                {
                    str = str.Replace("{" + firld + "k}{" + firld + "v}", "");
                }
                else
                {
                    str = str.Replace(str.Replace(",", "，").Replace("v}", ",").Split(',').First(s => s.Substring(s.Length - 1) == firld), "");
                }
            }
            return str;
        }
        /// <summary>
        /// 日志格式相关字段替换
        /// </summary>
        /// <param name="logType"></param>
        /// <param name="str"></param>
        /// <returns></returns>
        private static string FormatReplace(LogType logType, string str)
        {
            if (logType == LogType.Error || logType == LogType.Fatal)
            {
                //去掉{tk}{tv}相关
                str = TrimFirld(str, "t");
            }
            else
            {
                //去掉{pk}{pv}{ck}{cv}相关
                str = TrimFirld(str, "p");
                str = TrimFirld(str, "c");
            }
            str = str.Replace("dk", "0").Replace("dv", "1").Replace("tk", "2").Replace("tv", "3").Replace("pk", "4").Replace("pv", "5").Replace("ck", "6").Replace("cv", "7").Replace("mk", "8").Replace("mv", "9").Replace("nk", "10").Replace("nv", "11").Replace("lk", "12").Replace("lv", "13");
            return str;
        }
        /// <summary>
        /// 日志格式相关字段替换
        /// </summary>
        /// <param name="logType"></param>
        /// <param name="list"></param>
        /// <returns></returns>
        private static string FormatReplace(LogType logType, List<KeyValuePair<string, string>> list)
        {
            string str = string.Empty;
            if (logType == LogType.Error || logType == LogType.Fatal)
                str = "{dk}{dv}{pk}{pv}{ck}{cv}{mk}{mv}{nk}{nv}{lk}{lv}$n";
            else
                str = "{dk}{dv}{tk}{tv}{mk}{mv}{nk}{nv}{lk}{lv}$n";
            foreach (KeyValuePair<string, string> kv in list)
            {
                str = str.Replace(kv.Key, kv.Key.Contains("k}") && !kv.Value.Contains('：') && !kv.Value.Contains(':') ? kv.Value + "：" : kv.Value);
            }
            return FormatReplace(logType, str);
        }
        /// <summary>
        /// 获取目录地址
        /// </summary>
        /// <returns></returns>
        private static string GetFileDirectoryPath()
        {
            if (IsCreateDirectory)
                return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, strDirectory, DateTime.Now.ToString("yyyyMM"));
            else
                return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, strDirectory);
        }
        /// <summary>
        /// 获取文件地址
        /// </summary>
        /// <returns></returns>
        private static string GetFilePath()
        {
            //文件名
            string logFileName = DateTime.Now.ToString(strLogFileNameFormat) + ".txt";
            //文件路径
            return Path.Combine(GetFileDirectoryPath(), logFileName);
        }
        #endregion

        #region 读写文件
        /// <summary>
        /// 多线程方式创建文件
        /// </summary>
        public static void CreateLogFile()
        {
            new Action(delegate()
            {
                lock (lockObj)
                {
                    //检查目录是否存在
                    if (Directory.Exists(GetFileDirectoryPath()))
                    {
                        //目录存在
                        //检查文件是否存在
                        if (!File.Exists(GetFilePath()))
                        {
                            //文件不存在，创建文件
                            File.Create(GetFilePath()).Dispose();
                        }
                    }
                    else
                    {
                        //目录不存在
                        Directory.CreateDirectory(GetFileDirectoryPath());
                        File.Create(GetFilePath()).Dispose();
                    }
                }
            }).BeginInvoke(null, null);
        }
        /// <summary>
        /// 写日志
        /// 1.创建目录、文件（一开始运行时就默认创建，所以这里不用检查不用创建）
        /// 2.
        /// </summary>
        private static void WriteLog(string msg, LogType logType, Exception ex = null)
        {
            string strNewLine = Environment.NewLine;
            try
            {
                //获取日志格式
                string strContentFormat = GetContentFormat(logType, strNewLine);
                //获取对应消息值
                string strFileContent = string.Empty;
                string errMsg = string.Empty;
                string errMethod = string.Empty;
                string projectName = string.Empty;
                string location = string.Empty;
                if (logType == LogType.Error || logType == LogType.Fatal)
                {
                    if (ex != null)
                    {
                        errMsg = ex.Message;
                        errMethod = ex.TargetSite.ToString();
                        projectName = ex.Source;
                        location = ex.StackTrace;
                    }
                }

                strFileContent = string.Format(strContentFormat, "执行时间：", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + strNewLine, "提示消息：", msg + strNewLine, "错误提示：", msg + strNewLine, "错误信息：", errMsg + strNewLine, "操作方法：", errMethod + strNewLine, "项目名称：", projectName + strNewLine, "操作位置：", location + strNewLine);
                if (IsShowType)
                    strFileContent = "日志类型：" + logType + strNewLine + strFileContent;
                //写入文档
                lock (lockObj)
                {
                    using (StreamWriter sw = new StreamWriter(GetFilePath(), true))
                    {
                        sw.WriteLine(strFileContent);
                    }
                }
            }
            catch
            {
                CreateLogFile();
            }
        }
        #endregion


        #region Fatal
        public static void Fatal(object message)
        {
            Fatal(message.ToString(), null);
        }

        public static void Fatal(object message, Exception ex)
        {
            if (IsFatalEnabled)
            {
                //WriteLog(message.ToString(), LogType.Fatal, ex);
                Action<string, LogType, Exception> act = new Action<string, LogType, Exception>(WriteLog);
                act.BeginInvoke(message.ToString(), LogType.Fatal, ex, null, null);
            }
        }

        public static void FatalFormat(string format, object arg0)
        {
            Fatal(string.Format(format, arg0));
        }

        public static void FatalFormat(string format, object arg0, object arg1)
        {
            Fatal(string.Format(format, arg0, arg1));
        }

        public static void FatalFormat(string format, object arg0, object arg1, object arg2)
        {
            Fatal(string.Format(format, arg0, arg1, arg2));
        }

        public static void FatalFormat(string format, params object[] args)
        {
            Fatal(string.Format(format, args));
        }

        public static void FatalFormat(IFormatProvider provider, string format, params object[] args)
        {
            throw new NotImplementedException();
        }
        #endregion


        #region Error
        public static void Error(object message)
        {
            Error(message.ToString(), null);
        }

        public static void Error(object message, Exception ex)
        {
            if (IsErrorEnabled)
            {
                //WriteLog(message.ToString(), LogType.Error, ex);
                Action<string, LogType, Exception> act = new Action<string, LogType, Exception>(WriteLog);
                act.BeginInvoke(message.ToString(), LogType.Error, ex, null, null);
            }
        }

        public static void ErrorFormat(string format, object arg0)
        {
            Error(string.Format(format, arg0));
        }

        public static void ErrorFormat(string format, object arg0, object arg1)
        {
            Error(string.Format(format, arg0, arg1));
        }

        public static void ErrorFormat(string format, object arg0, object arg1, object arg2)
        {
            Error(string.Format(format, arg0, arg1, arg2));
        }

        public static void ErrorFormat(string format, params object[] args)
        {
            Error(string.Format(format, args));
        }

        public static void ErrorFormat(IFormatProvider provider, string format, params object[] args)
        {
            throw new NotImplementedException();
        }
        #endregion


        #region Warn
        public static void Warn(object message)
        {
            Warn(message.ToString(), null);
        }

        public static void Warn(object message, Exception ex)
        {
            if (IsWarnEnabled)
            {
                //WriteLog(message.ToString(), LogType.Warn, ex);
                Action<string, LogType, Exception> act = new Action<string, LogType, Exception>(WriteLog);
                act.BeginInvoke(message.ToString(), LogType.Warn, ex, null, null);
            }
        }

        public static void WarnFormat(string format, object arg0)
        {
            Warn(string.Format(format, arg0));
        }

        public static void WarnFormat(string format, object arg0, object arg1)
        {
            Warn(string.Format(format, arg0, arg1));
        }

        public static void WarnFormat(string format, object arg0, object arg1, object arg2)
        {
            Warn(string.Format(format, arg0, arg1, arg2));
        }

        public static void WarnFormat(string format, params object[] args)
        {
            Warn(string.Format(format, args));
        }

        public static void WarnFormat(IFormatProvider provider, string format, params object[] args)
        {
            throw new NotImplementedException();
        }
        #endregion


        #region Info
        public static void Info(object message)
        {
            Info(message.ToString(), null);
        }

        public static void Info(object message, Exception ex)
        {
            if (IsInfoEnabled)
            {
                //WriteLog(message.ToString(), LogType.Info, ex);
                Action<string, LogType, Exception> act = new Action<string, LogType, Exception>(WriteLog);
                act.BeginInvoke(message.ToString(), LogType.Info, ex, null, null);
            }
        }

        public static void InfoFormat(string format, object arg0)
        {
            Info(string.Format(format, arg0));
        }

        public static void InfoFormat(string format, object arg0, object arg1)
        {
            Info(string.Format(format, arg0, arg1));
        }

        public static void InfoFormat(string format, object arg0, object arg1, object arg2)
        {
            Info(string.Format(format, arg0, arg1, arg2));
        }

        public static void InfoFormat(string format, params object[] args)
        {
            Info(string.Format(format, args));
        }

        public static void InfoFormat(IFormatProvider provider, string format, params object[] args)
        {
            throw new NotImplementedException();
        }
        #endregion


        #region Debug
        public static void Debug(object message)
        {
            Debug(message, null);
        }

        public static void Debug(object message, Exception ex)
        {
            if (IsDebugEnabled)
            {
                //WriteLog(message.ToString(), LogType.Debug, ex);
                Action<string, LogType, Exception> act = new Action<string, LogType, Exception>(WriteLog);
                act.BeginInvoke(message.ToString(), LogType.Debug, ex, null, null);
            }
        }

        public static void DebugFormat(string format, object arg0)
        {
            Debug(string.Format(format, arg0));
        }

        public static void DebugFormat(string format, object arg0, object arg1)
        {
            Debug(string.Format(format, arg0, arg1));
        }

        public static void DebugFormat(string format, object arg0, object arg1, object arg2)
        {
            Debug(string.Format(format, arg0, arg1, arg2));
        }

        public static void DebugFormat(string format, params object[] args)
        {
            Debug(string.Format(format, args));
        }

        public static void DebugFormat(IFormatProvider provider, string format, params object[] args)
        {
            throw new NotImplementedException();
        }
        #endregion
    }
    enum LogType
    {
        Fatal = 1,
        Error = 2,
        Warn = 3,
        Info = 4,
        Debug = 5
    }
}
